import { Table } from './Table';
import { BooleanTypeConverters, DateTypeConverters, TimestampTypeConverters, TypeConverters } from './TypeConverters';
import { relationalStore } from '@kit.ArkData';
import { Internal } from '../common/Internal';

/**
 * Column 的通用类型
 */
export type ColumnType = Column<any, any, any, any>

/**
 * IndexColumn 的通用类型
 */
export type IndexColumnType = IndexColumn

/**
 * 支持在 SQLite 中声明的基本类型
 */
export type DataType = 'INTEGER' | 'TEXT' | 'BLOB' | 'REAL'

/**
 * 用于为 bindTo 提供一定的 IDE 检查
 */
type SafeKey<T, M> = {
  [K in keyof T]: T[K] extends (M | undefined) ? K : never
}[keyof T] extends infer U ? U extends string ? U : never : never

export class Column<F extends string, K extends string, W extends relationalStore.ValueType, R> {
  protected constructor(
    readonly fieldName: F,
    readonly dataType: DataType,
    readonly typeConverters: TypeConverters<W, R>
  ) {
  }

  tableName: string = null

  isPrimaryKey = false

  isAutoincrement = false

  isNotNull = false

  isUnique = false

  defaultStr: string = null

  key: string = null

  /**
   * 使用 PRIMARY KEY 修饰列
   *
   * @param autoincrement 是否是自增列
   * @returns this
   */
  primaryKey(autoincrement: boolean = false): Pick<Column<F, K, W, R>, 'primaryKey' | 'notNull' | 'unique' | 'default' | 'bindTo'> {
    this.isPrimaryKey = true
    if (autoincrement && this.dataType === 'INTEGER') {
      this.isAutoincrement = true
    } else if (autoincrement && this.dataType !== 'INTEGER') {
      throw new Error('The autoincrement option can only be set for INTEGER data types.')
    }
    return this
  }

  /**
   * 使用 NOT NULL 修饰列
   *
   * @returns this
   */
  notNull(): Pick<Column<F, K, W, R>, 'primaryKey' | 'notNull' | 'unique' | 'default' | 'bindTo'> {
    this.isNotNull = true
    return this
  }

  /**
   * 使用 UNIQUE 修饰列
   *
   * @returns this
   */
  unique(): Pick<Column<F, K, W, R>, 'primaryKey' | 'notNull' | 'unique' | 'default' | 'bindTo'> {
    this.isUnique = true
    return this
  }

  /**
   * 设置列的默认值
   *
   * @param value 默认值
   * @returns this
   */
  default(value: W): Pick<Column<F, K, W, R>, 'primaryKey' | 'notNull' | 'unique' | 'default' | 'bindTo'> {
    this.defaultStr = value?.toString()
    return this
  }

  /**
   * 将列绑定到目标表中实体模型的指定属性
   *
   * @param targetTable 目标表
   * @param key 实体中指定属性名
   * @returns this
   */
  bindTo<T, Key extends string & SafeKey<T, R>>(targetTable: Table<T>,
    key: Key): Column<F, Key, W, R> {
    this.key = key
    this.tableName = targetTable.tableName
    const useOptions = targetTable[Internal.TableOptions]
    useOptions.addColumn(this)
    Object.freeze(this)
    return this
  }

  /**
   * 修改别名
   *
   * @param fieldName 新的别名
   * @returns 指定别名的列
   */
  as<F extends string>(fieldName: F): Column<F, K, W, R> {
    return new AsColumn(this, fieldName)
  }

  /**
   * 创建 INTEGER 类型的列
   *
   * @param fieldName 列名
   * @returns Column 实例
   */
  static integer<F extends string, WriteType = number>(
    fieldName: F,
    typeConverters?: TypeConverters<number, WriteType>) {
    return new Column(fieldName, 'INTEGER', typeConverters)
  }

  /**
   * 创建 REAL 类型的列
   *
   * @param fieldName 列名
   * @returns Column 实例
   */
  static real<F extends string, WriteType = number>(fieldName: F,
    typeConverters?: TypeConverters<number, WriteType>) {
    return new Column(fieldName, 'REAL', typeConverters)
  }

  /**
   * 创建 TEXT 类型的列
   *
   * @param fieldName 列名
   * @returns Column 实例
   */
  static text<F extends string, WriteType = string>(fieldName: F,
    typeConverters?: TypeConverters<string, WriteType>) {
    return new Column(fieldName, 'TEXT', typeConverters)
  }

  /**
   * 创建 Uint8Array 类型的列
   *
   * @param fieldName 列名
   * @returns Column 实例
   */
  static blob<F extends string>(fieldName: F, typeConverters?: TypeConverters<Uint8Array, Uint8Array>) {
    return new Column(fieldName, 'BLOB', typeConverters)
  }

  /**
   * 创建 INTEGER 类型的列并通过 BooleanTypeConverters 将类型转换为 boolean
   *
   * @see BooleanTypeConverters
   * @param fieldName 列名
   * @returns Column 实例
   */
  static boolean<F extends string>(fieldName: F) {
    return new Column(fieldName, 'INTEGER', BooleanTypeConverters)
  }

  /**
   * 创建 TEXT 类型的列并通过 DateTypeConverters 将类型转换为 Date
   *
   * @see DateTypeConverters
   * @param fieldName 列名
   * @returns Column 实例
   */
  static date<F extends string>(fieldName: F) {
    return new Column(fieldName, 'TEXT', DateTypeConverters)
  }

  /**
   * 创建 INTEGER 类型的列并通过 TimestampTypeConverters 将类型转换为 Date
   *
   * @see TimestampTypeConverters
   * @param fieldName 列名
   * @returns Column 实例
   */
  static timestamp<F extends string>(fieldName: F) {
    return new Column(fieldName, 'INTEGER', TimestampTypeConverters)
  }

  /**
   * 将列绑定到参考表中，相当于关系数据库中的外键，使用时需要确保参考表和其实体都存在唯一主键
   * Storm 会将参考表中实体的主键存储到这个列上，在查询时 Storm 会自动从参考表中查询并填充到这个列所绑定的实体属性上
   *
   * @param fieldName 列名
   * @param referencesTable 参考的表
   * @returns ReferencesColumn 实例
   */
  static references<F extends string, ReadType>(fieldName: F, referencesTable: Table<ReadType>) {
    return new ReferencesColumn(fieldName, referencesTable)
  }

  /**
   * 创建索引列
   *
   * @param fieldName 列名
   * @returns IndexColumn 实例
   */
  static index(fieldName: string): Pick<IndexColumn, 'column' | 'unique'> {
    return new IndexColumn(fieldName)
  }
}

export class ReferencesColumn<F extends string, K extends string, R>
  extends Column<F, K, relationalStore.ValueType, R> {
  constructor(
    readonly fieldName: F,
    readonly referencesTable: Table<R>
  ) {
    super(fieldName, 'INTEGER', null)
  }
}

export class AsColumn<F extends string> extends Column<F, any, any, any> {
  readonly asFieldName: string

  constructor(targetColumn: ColumnType, asFieldName: string) {
    super(targetColumn.fieldName, null, null)
    this.asFieldName = asFieldName
    this.tableName = targetColumn.tableName
  }
}

/**
 * 索引的顺序
 */
type Order = 'ASC' | 'DESC'

export class IndexColumn {
  constructor(readonly fieldName: string) {
  }

  columns: [ColumnType, Order | undefined][] = []

  isUnique = false

  /**
   * 使用 UNIQUE 修饰索引列
   * @returns this
   */
  unique(): this {
    this.isUnique = true
    return this
  }

  /**
   * 向索引中添加列
   *
   * @param column 要添加的列
   * @param order 列的排序
   * @returns this
   */
  column(column: ColumnType, order?: Order): Pick<IndexColumn, 'column' | 'bindTo'> {
    this.columns.push([column, order])
    return this
  }

  /**
   * 将索引列绑定到目标表中
   *
   * @param targetTable 目标表
   * @returns this
   * @throws 如果索引不包含任何列或存在重复列，则抛出错误。
   */
  bindTo<T>(targetTable: Table<T>): this {
    if (this.columns.length === 0) {
      throw new Error('The index must contain at least one column.')
    }
    const columnFieldName = this.columns.map(([column]) => column.fieldName)
    if (new Set(columnFieldName).size !== columnFieldName.length) {
      throw new Error('Duplicate columns exist in the index.')
    }
    const useOptions = targetTable[Internal.TableOptions]
    useOptions.addColumn(this)
    Object.freeze(this)
    return this
  }
}